﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Configuration;
using FunctionalCLR.Core;

namespace FunctionalCLR.Shell {
    public class Shell {

        private string input = string.Empty;

        private string output = string.Empty;

        private Interpreter intp = null;

        private int count = 1;

        private Dictionary<string, ICommand> commands
            = new Dictionary<string, ICommand>();

        static void Main(string[] args) {
            Console.Title = "FunctionalCLR Shell";
            Console.WriteLine("Copyright 2009 Zove Development Team\n");

            Context context = new Context();
            Interpreter intp = new Interpreter(context);
            Shell p = new Shell(intp);
            if (args.Length > 0) {
                List<string> programArgs = new List<string>();
                if (args.Length > 1) {
                    for (int index = 1; index < args.Length; index++) {
                        programArgs.Add(args[index]);
                    }
                }
                intp.Run(args[0], programArgs.ToArray());
                return;
            }

            p.StartSession();
            Console.WriteLine("Session ends");
        }

        public Shell(Interpreter intp) {
            this.intp = intp;
        }

        public void LoadSession(string fileName) {
            FileStream fs = new FileStream(fileName, FileMode.Open);
            StreamReader sr = new StreamReader(fs);

            string input = string.Empty;
            while ((input = sr.ReadLine()) != null) {
                Console.WriteLine(
                    string.Format("in[{0}] = {1}", count, input));
                object output = intp.Process(input);
                Console.WriteLine(
                    string.Format("out[{0}] = {1}", count++, output));
            }

            sr.Close();
        }

        public string RunCommand(string input) {
            int index = input.IndexOf('(');
            int r = input.LastIndexOf(')');
            string name = input.Substring(0, index - 1);
            string param = input.Substring(index + 1, r - index - 1);

            ICommand command = this.GetCommand(name);
            if (command == null) {
                throw new Exception("Cannot find command " + name + ".");
            }

            return command.Execute(param);
        }

        public void InitCommands() {
            foreach (string p in ConfigurationManager.AppSettings.AllKeys) {
                if (p.StartsWith("cmd:")) {
                    string udf = ConfigurationManager.AppSettings[p];
                    this.AddCommand(p, udf);
                }
            }
        }

        public ICommand AddCommand(string commandName, string command) {
            string config = ConfigurationManager.AppSettings[commandName];
            if (string.IsNullOrEmpty(config)) {
                return null;
            }
            string assemblyName = config.Split(',')[0].Trim();
            string typeName = config.Split(',')[1].Trim();

            ICommand node
                = (ICommand)FunctionalCLR.Core.Utils.GetObject(
                assemblyName, typeName);
            if (node == null) {
                throw new Exception("Command " + commandName + " is not found.");
            }

            //node.Context = this.intp.Context;
            this.commands.Add(commandName, node);
            return node;
        }

        private ICommand GetCommand(string p) {
            if (IsDefinedCommand(p)) {
                return this.commands[p];
            }

            return null;
        }

        private bool IsDefinedCommand(string p) {
            return this.commands.ContainsKey(p);
        }

        public void StartSession() {
            Console.Write("in[{0}] := ", count);
            while (true) {
                input = Console.ReadLine();
                if (!string.IsNullOrEmpty(input)) {
                    if (!input.EndsWith(";")) {
                        input = input + ";";
                    }
                    if (input.Equals("exit;")) {
                        break;
                    }

                    try {
                        if (input.StartsWith("@")) {
                            output = this.RunCommand(input);
                        } else {
                            output = intp.Process(input).ToString();
                        }
                        Console.WriteLine(
                            string.Format("out[{0}] = {1}", count, output));
                    } catch (Exception ex) {
                        StreamWriter writer = File.AppendText(Utils.LogFile);
                        writer.WriteLine(ex.InnerException.Message + "\n" + ex.InnerException.StackTrace);

                        Console.WriteLine("Err[{0}] = {1}", count, ex.InnerException.Message);

                        writer.Close();
                    }
                }
                Console.Write(string.Format("in[{0}] := ", ++count));
            }
        }
    }
}
